// This file may be used under the terms of the GNU General Public License
// version 3.0 as published by the Free Software Foundation and appearing in
// the file LICENSE included in the packaging of this file.  Please review the
// following information to ensure the GNU General Public License version 3.0
// requirements will be met: http://www.gnu.org/copyleft/gpl.html.
// 
// If you do not wish to use this file under the terms of the GPL version 3.0
// then you may purchase a commercial license.  For more information contact
// info@riverbankcomputing.com.
// 
// This file is provided AS IS with NO WARRANTY OF ANY KIND, INCLUDING THE
// WARRANTY OF DESIGN, MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE.

#![crate_type = "dylib"]
#![allow(non_camel_case_types)]
#![allow(non_snake_case)]

use libc::c_char;
use log::{info, LevelFilter};
use once_cell::sync::OnceCell;
use tree_sitter::{Language, Parser, TreeCursor};
use std::str;

type FFI_PluginCallback = extern "C" fn(p_name: *const c_char, start_pos: libc::c_int) -> i32;

static G_PLUGIN_CB: OnceCell<FFI_PluginCallback> =  OnceCell::new();

#[no_mangle]
extern "C" fn ffi_outline_init(p_ffi_cb: FFI_PluginCallback) -> i32 {
    if G_PLUGIN_CB.get().is_some() {
        return -10;
    }

    let log_path = std::path::PathBuf::from("./plugin/hell_plugin/outline-rs.log");
    if log_path.exists() {
        let _ = simple_logging::log_to_file(log_path, LevelFilter::Info);
    }

    if G_PLUGIN_CB.set(p_ffi_cb).is_err() {
        return -1;
    }

    return 0;
}

#[derive(Debug)]
struct FuncPos {
    pub start_byte: usize,
    pub end_byte: usize,
}

fn node_check_cs(cursor: &mut TreeCursor) -> Option<FuncPos> {
    // >#cs#### method_declaration: private void ClearLog()
    // >#cs##### modifier: private
    // >#cs##### predefined_type: void
    // >#cs##### identifier: ClearLog
    let node0 = cursor.node();
    if node0.kind() == "method_declaration" {
        if cursor.goto_first_child() {
            loop {
                let node1 = cursor.node();
                if node1.kind() == "identifier" {
                    return Some(FuncPos {
                        start_byte: node1.start_byte(),
                        end_byte: node1.end_byte(),
                    });
                }
                if !cursor.goto_next_sibling() {
                    break;
                }
            }
            cursor.goto_parent();
        }
    }

    return None;
}

fn node_check_rust(cursor: &mut TreeCursor) -> Option<FuncPos> {
    // >#rust# fn: fn
    // >#rust# identifier: start_download
    let node0 = cursor.node();
    if node0.kind() == "fn" {
        if cursor.goto_next_sibling() {
            let node1 = cursor.node();
            if node1.kind() == "identifier" {
                return Some(FuncPos {
                    start_byte: node1.start_byte(),
                    end_byte: node1.end_byte(),
                });
            }
        }
    }

    return None;
}

fn node_check_java(cursor: &mut TreeCursor) -> Option<FuncPos> {
    // >##java## boolean_type: boolean    //endwith("_type")
    // >##java## identifier: onCreateOptionsMenu
    let node0 = cursor.node();
    if node0.kind().ends_with("_type") {
        let type_start = node0.start_byte();
        if cursor.goto_next_sibling() {
            let node1 = cursor.node();
            if node1.kind() == "identifier" {
                return Some(FuncPos {
                    start_byte: type_start,
                    end_byte: node1.end_byte(),
                });
            }
        }
    }

    return None;
}
fn node_check_js(cursor: &mut TreeCursor) -> Option<FuncPos> {
    // >#js# function: function
    // >#js# identifier: changeBaudTo115200bps
    let node0 = cursor.node();
    if node0.kind() == "function" {
        if cursor.goto_next_sibling() {
            let node1 = cursor.node();
            if node1.kind() == "identifier" {
                return Some(FuncPos {
                    start_byte:node1.start_byte(),
                    end_byte: node1.end_byte(),
                });
            }
        }
    }

    return None;
}
fn node_check_py(cursor: &mut TreeCursor) -> Option<FuncPos> {
    // >#py# function_definition: def __lrc(self, data, l):
    let node0 = cursor.node();
    if node0.kind() == "function_definition" {
        return Some(FuncPos {
            start_byte: node0.start_byte() + 4,//skip def 
            end_byte: node0.end_byte(),
        });
    }

    return None;
}
fn node_check_c(cursor: &mut TreeCursor) -> Option<FuncPos> {
    // >#c # primitive_type: void  (not else)
    // >#c # function_declarator: ReplyToHost(uint16_t usErr)
    let node0 = cursor.node();
    if node0.kind() == "primitive_type" || node0.kind().contains("identifier") {
        let type_start = node0.start_byte();
        while cursor.goto_next_sibling() {
            let node1 = cursor.node();
            if node1.kind() == "function_declarator" {
                return Some(FuncPos {
                    start_byte: type_start,
                    end_byte: node1.end_byte(),
                });
            }
        }
    }

    return None;
}

fn traverse_tree_node<CB>(level: &str, code: &str, cursor: &mut TreeCursor, fn_node_check: &CB) -> Vec<FuncPos> 
    where CB:
        Fn(&mut TreeCursor) -> Option<FuncPos>
{
    let mut FuncPosList: Vec<FuncPos> = Vec::new();

    loop {
        // let node = cursor.node();
        // if let Ok(name) = str::from_utf8(&code[node.start_byte()..node.end_byte()].as_bytes()) {
        //     info!("node: {}", name);
        // }
        if let Some(func) = fn_node_check(cursor) {
            FuncPosList.push(func);
        }

        if cursor.goto_first_child() {
            let mut l = level.to_string();
            l.push('#');
            FuncPosList.extend(traverse_tree_node(&l, code, cursor, fn_node_check));
            cursor.goto_parent();
        }

        if !cursor.goto_next_sibling() {
            break;
        }
    }

    return FuncPosList;
}

fn get_function_list(code: &str, ext_lowercase: &str) -> Option<Vec<FuncPos>>{
    let mut functions: Option<Vec<FuncPos>> = None;
    let mut parser = Parser::new();
    let tree;
    match ext_lowercase {
        "c" | "cpp" | "cxx" => {
            let lang = &tree_sitter_c::language();
            parser.set_language(lang).unwrap();
            tree = parser.parse(code, None).unwrap();
            let root_node = tree.root_node();
            functions= Some(traverse_tree_node(">", code, &mut root_node.walk(), &node_check_c))
        }
        "js" | "ts" => {
            let lang: &Language = &tree_sitter_javascript::language();
            parser.set_language(lang).unwrap();
            tree = parser.parse(code, None).unwrap();
            let root_node = tree.root_node();
            functions= Some(traverse_tree_node(">", code, &mut root_node.walk(), &node_check_js))
        }
        "java" => {
            let lang = &tree_sitter_java::language();
            parser.set_language(lang).unwrap();
            tree = parser.parse(code, None).unwrap();
            let root_node = tree.root_node();
            functions= Some(traverse_tree_node(">", code, &mut root_node.walk(), &node_check_java))
        }
        "py" => {
            let lang = &tree_sitter_python::language();
            parser.set_language(lang).unwrap();
            tree = parser.parse(code, None).unwrap();
            let root_node = tree.root_node();
            functions= Some(traverse_tree_node(">", code, &mut root_node.walk(), &node_check_py))
        }
        "rs" => {
            let lang = &tree_sitter_rust::language();
            parser.set_language(lang).unwrap();
            tree = parser.parse(code, None).unwrap();
            let root_node = tree.root_node();
            functions= Some(traverse_tree_node(">", code, &mut root_node.walk(), &node_check_rust))
        }
        "cs" => {
            let lang = &tree_sitter_c_sharp::language();
            parser.set_language(lang).unwrap();
            tree = parser.parse(code, None).unwrap();
            let root_node = tree.root_node();
            functions= Some(traverse_tree_node(">", code, &mut root_node.walk(), &node_check_cs))
        }
        _ => info!("Unsupported file extension"),
    }
    
    return functions;
}

fn func_name_format(func_name: &str, ext_lowercase: &str) -> String {
    match ext_lowercase {
        "c" | "cpp" | "cxx" => {
            let mut f_name = func_name.to_string();
            if f_name.contains("\r") {
                f_name = f_name.replace("\r", "");
            }
            if f_name.contains("\n") {
                f_name = f_name.replace("\n", "");
            }
            if f_name.contains("\t") {
                f_name = f_name.replace("\t", "");
            }
            while f_name.contains("  ") {
                f_name = f_name.replace("  ", " ");
            }

            return f_name;
        }
        "py" => {
            if func_name.contains(":") {
                if let Some(name) = func_name.split(":").next() {
                    return name.to_string();
                }
            }
        }
        _ => {}
    }

    func_name.to_string()
}

#[no_mangle]
pub extern "C" fn ffi_outline_reflash(p_code: *const c_char, code_size: u32, 
                                        p_ext: *const c_char, ext_size: u32) -> i32 {

    if p_code.is_null() {
        return -1;
    }

    let code = buffer_to_str(p_code, code_size);
    let ext  = buffer_to_str(p_ext, ext_size).to_lowercase();
    info!("ext: {}", ext);

    if let Some(ffi_cb) = G_PLUGIN_CB.get() {
        let c_text = std::ffi::CString::new(format!("Outline .{}", ext)).unwrap();
        ffi_cb(c_text.as_ptr() as *const c_char, -1);

        if let Some(functions) = get_function_list(&code, &ext) {
            for pos in functions {
                if let Ok(func_name) = str::from_utf8(&code[pos.start_byte..pos.end_byte].as_bytes()) {
                    let func_name = func_name_format(func_name, &ext);
                    info!("{} : {}", func_name, pos.start_byte);
                    let c_text = std::ffi::CString::new(func_name).unwrap();
                    ffi_cb(c_text.as_ptr() as *const c_char, pos.start_byte as libc::c_int);
                }
            }
        }
    }

    return 0;
}

fn buffer_to_str(p_char: *const c_char, size: u32) -> String
{
    info!("buffer_to_str:");
    if p_char.is_null() {
        return String::new();
    }
    let text_vec = unsafe {
        std::slice::from_raw_parts(p_char as *const u8, size as usize).to_vec()
    };
    
    let text = String::from_utf8(text_vec).unwrap_or_default();
    info!("    {}", text);
    return text;
}
